import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'; // prop-types类型限制包
import './index.css';

/* 1 - JSX */
const data = ['Angular', 'React', 'Vue'];
const element1 = (
    <div>
        <h1>JS框架列表</h1>
        <ul>
            {
                data.map((item, index) => {
                    return <li key={index}>{item}</li>;
                })
            }
        </ul>
    </div>
);

/* 2 - 组件 */
function Element2A(props) {
    return <h2>函数式组件, 用于简单组件, 无state</h2>
}
class Element2B extends React.Component {
    render() {
        return <h2>类式组件, 用于复杂组件</h2>
    }
}

/* 3 - state */
class Element3 extends React.Component {
    // constructor(props) {
    //     super(props);
    //     // this.state = { isHot: true };
    //     // this.handleClick = this.handleClick.bind(this);
    // }

    // state简写
    state = { isHot: true };

    // 这样写, handleClick作为自定义方法, 不在原型上
    handleClick = () => {
        this.setState(
            { isHot: !this.state.isHot }
        );
    }

    // ref回调次数演示
    saveEg = (c) => {
        this.saveEg1 = c;
        console.log('@', c);
    }

    render() {
        return <h2 ref={this.saveEg} onClick={this.handleClick}>今天天气很{this.state.isHot ? '炎热' : '寒冷'}</h2>
    }
}

/* 4 - props */
const tom = { name: "Tom", age: 18/* , sex: 'women' */ };
class Element4 extends React.Component {
    // 不初始化state, 或不为事件处理函数绑定实例时, 无需构造器
    // constructor(props) {
    //     super(props);
    // }

    // static给类自身加属性, 比在类外添加更清晰, 函数式组件需要定义在外侧
    // props类型限制, 限制函数为func
    static propTypes = {
        name: PropTypes.string.isRequired, // 必须
        sex: PropTypes.string,
        age: PropTypes.number
    }
    static defaultProps = { // 默认
        sex: 'unknown',
        age: 'unknown'
    }

    render() {
        const { name, age, sex } = this.props;
        return (
            <ul>
                <li>姓名: {name}</li>
                <li>性别: {sex}</li>
                <li>年龄: {age + 1}</li>
            </ul>
        );
    }
}
function Element5(props) {
    // 类型限制需要定义在外面
    const { name, age, sex } = props;
    return (
        <ul>
            <li>姓名: {name}</li>
            <li>性别: {sex}</li>
            <li>年龄: {age + 1}</li>
        </ul>
    );
}

/* 5 - refs */
// 字符串形式ref(过时)
class Element6 extends React.Component {
    handleClick = () => {
        console.log(this.refs.input1.value);
    }
    handleBlur = () => {
        console.log(this.refs.input2.value);
    }
    render() {
        return (
            <div>
                {/* ref中key:value, key为ref值, value是当前ref所处节点(真实DOM) */}
                <input ref="input1" type="text" placeholder='点击按钮提示数据' />
                <button onClick={this.handleClick}>字符串形式ref</button>
                <br />
                <input ref="input2" onBlur={this.handleBlur} type="text" placeholder='失去焦点提示数据' />
            </div>
        );
    }
}
// 回调形式ref
class Element7 extends React.Component {
    handleClick = () => {
        console.log(this.input1.value);
    }
    saveInput1 = (c) => {
        this.input1 = c;
        console.log('@', c);
    }
    render() {
        return (
            <div>
                <br />
                {/* ref回调的参数是ref所处的节点, 把节点放在组件实例上, 起名input1, c=currentNode */}
                {/* ref在重新渲染的过程中会执行两次, 第一次传入参数null, 第二次传入ref所处节点, 是为了清空旧的ref */}
                {/* (大多数情况下是无关紧要的)为了避免上述情况, 可以把ref的回调函数定义成组件的绑定函数 */}
                {/* <input ref={c => { this.input1 = c; }} type="text" placeholder='点击按钮提示数据' /> */}
                <input ref={this.saveInput1} type="text" placeholder='点击按钮提示数据' />
                <button onClick={this.handleClick}>回调形式ref</button>
            </div>
        );
    }
}
// createRef
class Element8 extends React.Component {
    myRef = React.createRef(); // 调用后返回容器, 存储被ref标识的节点, 但只能存一个
    handleClick = () => {
        console.log(this.myRef.current.value);
    }
    render() {
        return (
            <div>
                <br />
                <input ref={this.myRef} type="text" placeholder='点击按钮提示数据' />
                <button onClick={this.handleClick}>createRef</button>
            </div>
        );
    }
}

/* 6 - 事件处理 */
class Element9 extends React.Component {
    // React中使用自定义(合成)事件而非原生Dom事件, 是为了更好的兼容性
    // React中的时间通过事件委托(委托给组件最外层的元素), 是为了高效, 如ul-li结构, 事件会挂在ul上
    // 通过event.target获得发生事件的DOM元素对象

    handleBlur = (event) => {
        console.log(event.target.value);
    }
    render() {
        return (
            <div>
                <br />
                <input onBlur={this.handleBlur} type="text" placeholder='失去焦点提示数据' />
            </div>
        );
    }
}
// 非受控组件 - 现用现取(需要ref)
class Element10 extends React.Component {
    handleSubmit = (e) => {
        // console.log(`用户名:${this.state.username}, 密码:${this.state.password}`);
        console.log(`用户名:${this.username.value}, 密码:${this.password.value}`);
        e.preventDefault();
    }
    render() {
        return (
            <div>
                <br />
                <form onSubmit={this.handleSubmit}>
                    用户名: <input type="text" ref={c => this.username = c} />&nbsp;
                    密码: <input type="password" ref={c => this.password = c} />
                    <button>非受控组件</button>
                </form>
            </div>
        );
    }
}
// 受控组件
class Element11 extends React.Component {
    state = {
        username: '',
        password: ''
    }
    handleSubmit = (e) => {
        const { username, password } = this.state; // 解构赋值
        console.log(`用户名:${username}, 密码:${password}`);
        e.preventDefault();
    }
    saveUsername = (e) => {
        this.setState({ username: e.target.value });
    }
    savePassword = (e) => {
        this.setState({ password: e.target.value });
    }
    render() {
        return (
            <div>
                <br />
                <form onSubmit={this.handleSubmit}>
                    用户名: <input type="text" onChange={this.saveUsername} />&nbsp;
                    密码: <input type="password" onChange={this.savePassword} />
                    <button>受控组件</button>
                </form>
            </div>
        );
    }
}

/* 7 - 利用高阶函数进行优化 */
class Element12 extends React.Component {
    /* 
        高阶函数: 1> 一个函数A接收的参数时一个函数, 则A为高阶函数; 2> 一个函数A调用的返回值是一个函数, 则A为高阶函数
        函数柯里化: 通过函数调用继续返回函数的方式, 实现多次接收参数最后统一处理的函数编码形式
        */
    state = {
        username: '',
        password: ''
    }
    handleSubmit = (e) => {
        const { username, password } = this.state; // 解构赋值
        console.log(`用户名:${username}, 密码:${password}`);
        e.preventDefault();
    }

    // onchange的回调是已经调用的函数, 即该函数的返回值(闭包), 这里是一个柯里化
    saveFormData1 = (dataType) => {
        return (event) => { // 该函数为onChange的回调
            // 原本键为字符串, 直接写dataType是字符串, 需要[]把字符串变为变量作为键
            this.setState({ [dataType]: event.target.value });
        }
    }

    saveFormData2 = (dataType, event) => {
        this.setState({ [dataType]: event.target.value });
    }

    render() {
        return (
            <div>
                <br />
                <form onSubmit={this.handleSubmit}>
                    用户名: <input type="text" onChange={this.saveFormData1('username')} />&nbsp;
                    密码: <input type="password" onChange={(event) => { this.saveFormData2('password', event) }} />
                    <button>函数柯里化</button>
                </form>
            </div>
        );
    }
}

/* 8 - 生命周期 */
class Element13 extends React.Component {
    state = { opacity: 1 }

    // 组件挂载完毕时调用, 生命周期钩子函数
    componentDidMount() {
        this.timerID1 = setInterval(() => this.fadeOut(), 1000);
    }

    fadeOut = () => {
        let { opacity } = this.state;
        opacity -= 0.2;
        if (opacity <= 0) opacity = 1;
        this.setState({ opacity });
    }

    // 组件将要卸载
    componentWillUnmount() {
        clearInterval(this.timerID1);
    }

    death = () => {
        // clearInterval(this.timerID1);
        ReactDOM.unmountComponentAtNode(document.getElementById('root'));
    }

    // render在初始化渲染和状态更新后调用
    render() {
        return (
            <div>
                <h2 style={{ opacity: this.state.opacity }}>React生命周期</h2>
                <button onClick={this.death}>LifeCycle</button>
            </div>
        );
    }
}
// 旧生命周期
class Element14 extends React.Component {
    constructor(props) {
        console.log('old constructor');
        super(props);
        this.state = { count: 0 };
    }

    add = () => {
        const { count } = this.state;
        this.setState({ count: count + 1 });
    }

    death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('root'));
    }

    force = () => {
        this.forceUpdate(); // 强制更新, 不改状态也能更新
    }

    // 将要挂载
    componentWillMount() {
        console.log('old willMount');
    }

    // 挂载完毕
    componentDidMount() {
        console.log('old didMount');
    }

    // 将要卸载
    componentWillUnmount() {
        console.log('old willUnmount');
    }

    // 将要更新
    componentWillUpdate() {
        console.log('old componentWillUpdate');
    }

    // 阀门, 更新时执行
    shouldComponentUpdate() {
        console.log('old shouldComponentUpdate');
        return true; // 默认true
    }

    // 更新完毕
    componentDidUpdate() {
        console.log('old componentDidUpdate');
    }

    render() {
        console.log('old render');
        const { count } = this.state;
        return (
            <div>
                <h3>旧生命周期 - 当前点击{count}次</h3>
                <button onClick={this.add}>点我+1</button>
                <button onClick={this.death}>点我卸载</button>
                <button onClick={this.force}>点我强制更新</button>
                <OldCom times={this.state.count} />
            </div>
        );
    }
}
class OldCom extends React.Component {
    componentWillReceiveProps(props) { // 首次不调用
        console.log('oldCom willReceiveProps', props);
    }

    // 将要更新
    componentWillUpdate() {
        console.log('oldCom componentWillUpdate');
    }

    // 阀门, 更新时执行
    shouldComponentUpdate() {
        console.log('oldCom shouldComponentUpdate');
        return true; // 默认true
    }

    // 更新完毕
    componentDidUpdate() {
        console.log('oldCom componentDidUpdate');
    }

    render() {
        return (
            <span> 当前点击:{this.props.times}次</span>
        );
    }
}
// 新生命周期
class Element15 extends React.Component {
    constructor(props) {
        console.log('new constructor');
        super(props);
        this.state = {
            count: 0,
            newsArr: []
        };
    }

    add = () => {
        const { count } = this.state;
        this.setState({ count: count + 1 });
    }

    death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('root'));
    }

    force = () => {
        this.forceUpdate(); // 强制更新, 不改状态也能更新
    }

    changeNews = () => {
        const { newsArr } = this.state;
        // 模拟新闻
        const news = '新闻' + (newsArr.length + 1);
        this.setState({
            // 把新新闻更新到数组开头处
            newsArr: [news, ...newsArr]
        });
    }

    static getDerivedStateFromProps(props, state) {
        // 若state的值任何是取决于props, 可以使用
        console.log('new getDerivedStateFromProps', props, state);
        return null;
        // return state对象或null
    }

    // 在最近一次渲染输出之前调用, 获得组件发生更改之前的信息, 将返回值作为参数传递给componentDidUpdate()
    getSnapshotBeforeUpdate() {
        console.log('new getSnapshotBeforeUpdate');
        return this.list.scrollHeight;
        // return snapshot对象或null
    }

    // 更新完毕
    componentDidUpdate(prevProps, prevState, height) {
        console.log('new componentDidUpdate', prevProps, prevState);
        this.list.scrollTop += this.list.scrollHeight - height;
    }

    // 挂载完毕
    componentDidMount() {
        console.log('new didMount');
        setInterval(this.changeNews, 1000);
    }

    // 将要卸载
    componentWillUnmount() {
        console.log('new willUnmount');
    }

    // 阀门, 更新时执行
    shouldComponentUpdate() {
        console.log('new shouldComponentUpdate');
        return true; // 默认true
    }

    render() {
        console.log('new render');
        const { count } = this.state;
        return (
            <div>
                <h3>新生命周期 - 当前点击{count}次</h3>
                <button onClick={this.add}>点我+1</button>
                <button onClick={this.death}>点我卸载</button>
                <button onClick={this.force}>点我强制更新</button>
                <div className='list' ref={c => this.list = c}>
                    {this.state.newsArr.map((news, index) => { return <div key={index} className='news'>{news}</div> })}
                </div>
            </div>
        );
    }
}

/* 9 - diff算法 */
class Element16 extends React.Component {
    state = {
        persons: [
            { id: 1, name: 'haha', age: 18 },
            { id: 2, name: 'lili', age: 19 },
        ]
    };

    add = () => {
        const {persons} = this.state;
        const p = {
            id: persons.length + 1,
            name: 'mimi',
            age: 20
        };
        // 此时把新数据放在了数组最前面, 如果key用索引的话则影响了后面全部的元素, 所有数据都需要重新渲染
        this.setState({persons: [p, ...persons]})
    }

    render() {
        return (
            <div>
                {/* 如果有input, index作为key就会出现bug */}
                {/* index作为key, 对数据进行逆序操作会引发问题, 产生没有必要的真实DOM更新, 影响效率 */}
                <h4>展示人员信息 - index为key</h4>
                <ul>
                    {this.state.persons.map((p, index) => {
                        return <li key={index}>{p.name},{p.age},{p.id}<input type="text" /></li>
                    })}
                </ul>
                <h4>展示人员信息 - id为key</h4>
                <ul>
                    {this.state.persons.map((p, index) => {
                        return <li key={p.id}>{p.name},{p.age},{p.id}<input type="text" /></li>
                    })}
                </ul>
                <button onClick={this.add}>添加mimi</button>
            </div>
        );
    }
}

ReactDOM.render(
    <div>
        {element1}
        <Element2A />
        <Element2B />
        <Element3 />
        {/* 批量传递, 这里的...与JS中的扩展运算符不同, 是可以展开对象的, props={...obj} */}
        <Element4 {...tom} />
        <Element5 {...tom} />
        <Element6 />
        <Element7 />
        <Element8 />
        <Element9 />
        <Element10 />
        <Element11 />
        <Element12 />
        {/* <Element13 /> */}
        <Element14 />
        {/* <Element15 lifestyle="new" /> */}
        <Element16 />
    </div>,
    document.getElementById('root')
);